""" Implements container widget functionality. """

from weakref import WeakKeyDictionary
from operator import attrgetter

from pygame import Rect

from sbak.event import (
    Forwarder,
    EventManager
)

from sbak.widget import (
    AbstractWidget
)

__all__ = [
    'AbstractContainer',
    'Container'
]

class AbstractContainer(AbstractWidget):
    """ Implements features common to all types of container.
    
        This class will not function stand-alone and must be inherited
        by a class that implements the missing functionality of a
        container widget.
        FIXME: What functionality is this? Be specific!
    """
    
    def __init__(self, region=None):
        AbstractWidget.__init__(self, region)
    
    def _update_regions(self):
        super(AbstractContainer,self)._update_regions()
        for child in self:
            child._update_regions()
    
    def display(self, surface=None, offset=(0,0), region=None):
        """ Displays the container and/or its children.
        
            surface:    Surface onto which the container will be
                        displayed. If unspecified, the parent
                        container's surface will be assumed.
            
            offset:     An offset from the container's region's position.
                        The container will be drawn at its own position
                        moved by the amount of this offset.
            
            region:     The region of the surface that is being
                        displayed. If one is given, the container will not
                        display anything outside of this region of the
                        destination surface.
            
            This will display the container and/or its contents onto the
            surface specified. If no region is given, any areas within
            this container that have been marked as dirty will be cleaned.
            
            If a region IS given, only the regions of the container that
            are completely inside the region will be marked as clean.
            If no part of the container touches the region, this method
            will do nothing.
            
            Calling this will also tell the container to display any
            widgets that are within the region.
            
            If the surface of the container has the same absolute
            parent surface as the surface of the container's parent
            (meaning that the current container's surface and the
            parent container's surface are both part of the same parent
            surface) then only the children are drawn, and the current
            container itself is NOT drawn. (This is to prevent a
            surface lock error from occuring.)
        """
        
        # Determine which surface we're drawing ourselves to.
        if surface is None:
            surface = self.parent.surface
            
        # Create a localized translation of the container's own displayed region.
        mylocal = Rect((0,0), self.displayed_region.size)
        
        # If no region is specified, display the entire container and all widgets.
        if region is None:
            
            # Get the surface for children to draw to.
            mysurface = self.surface
            
            # Draw a box for the container onto the container's own surface.
            self.draw_internal(mysurface, mylocal)
            
            # Display each widget onto the container's own surface.
            for widget in self:
                widget.display(mysurface)
                
            # Finally, display the container onto the given surface, but
            # only if the container's own surface is not a part of the
            # parent's surface.
            if mysurface.get_abs_parent() is not surface.get_abs_parent():
                surface.blit(mysurface, self.displayed_region.move(offset).topleft)
        
        # If a region is specified and the regions overlap,
        # draw the parts within the region.
        elif self.displayed_region.colliderect(region):
            
            # Turn the region into a rect, if it is not one already.
            region = Rect(region)
            
            # Get a local translation of the region.
            tregion = region.move([-p for p in self.region.topleft])
            
            # Get the surface for children to draw to.
            mysurface = self.surface
            
            # Draw the widget box in the affected area
            oldclip = mysurface.get_clip()
            mysurface.set_clip(tregion)
            self.draw_internal(mysurface, mylocal)
            mysurface.set_clip(oldclip)
            del oldclip
            
            # Display each widget in the affected area
            for widget in self:
                if tregion.colliderect(widget.region):
                    widget.display(mysurface, region=tregion)
            
            # Finally, display the relavant region of the container onto the
            # given surface, but only if the container's surface is not a
            # part of the parent's surface.
            if mysurface.get_abs_parent() is not surface.get_abs_parent():
                surface.blit(
                    mysurface,
                    self.displayed_region.move(offset).move(tregion.topleft).topleft,
                    tregion
                )
    
    def close(self, e=None):
        """ Removes the container from the parent.
        
            e:    This is generally ignored. It exists so that the
                    `close` method can be easily used as an event
                    handler.
        
            This will also smudge the parent in the region the container
            was occupying.
        """
        p = self.parent
        if p is not None:
            p.remove(self)
        if hasattr(p, 'smudge'):
            p.smudge(self.region)
    
    def kill(self):
        """ Kills the container, removing it from its parent.
        
            This will also smudge the parent in the region the container
            was occupying, and will remove all children from the
            container, and will unhook all the container's hooks.
            
            To simply remove the container from the parent without
            removing the children fron the container, use
            `Window.close` instead.
            
            Safe to call multiple times. Only call this if you do not
            plan on using the container anymore.
        """
        p = self.parent
        super(AbstractContainer, self).kill()

class Container(EventManager, AbstractContainer):
    """ Basic container for widgets.
    
        cont = Container(region = None, widgets = ())
        
        ~~~ Constructor Arguments ~~~
        
        region:     Region occupied by container in the parent. This
                    won't really matter for most types of container
                    since they tend to alter their own region to fit
                    the region of their parents.
        
        widgets:    Widgets to place into the container.
    
        ~~~ Description ~~~
    
        Containers hold one or more widgets and forward events and
        commands received on to their children.
        
        Containers are different from windows in that they do not
        actually have their own surface. The `surface` property
        dynamically generates a subsurface of the parent's surface
        based on the container's region.
        
        Because of the above, changes to a container's region will
        cause all widgets within the container to become completely
        dirty.
    """
    
    def __init__(self, region=None, widgets=(), id=None):
        EventManager.__init__(self, widgets, id = id)
        AbstractContainer.__init__(self, region)
    
    def move(self, offset):
        """ Move the container relative to its current position.
        
            This will offset the container from its current position
            within its parent.
        """
        super(Container,self).move(offset)
    
    def place(self, pos):
        """ Place the container at a position within the parent.
        
            This will move the container to a new position
            within its parent.
        """
        super(Container,self).place(pos)
    
    def resize(self, size):
        """ Change the size of the container to a new size..
        
            This will change the size of the container's occupied
            region.
        """
        super(Container,self).resize(self, size)
    
    _doc_region =\
        """ Region the container occupies in the parent.
        """
    def _fset_region(self, region):
        super(Container,self)._fset_region(region)


    @property
    def surface(self):
        """ Gives a surface that is a subsurface of the parent's.
            
            This returns a subsurface of the parent's surface that has
            the same region as widget.
            
            Read-only.
        """
        return self.parent.surface.subsurface(self.region)
